/*
 *  Copyright 2010 Wesley Gooch, Ryan Nix, Cody Wenzel.
 *  Montana State University - Spring Semester 2010
 *  Computer Science 450 - Compilers
 *  Project: Micro-Pascal Compiler
 */
package msu.compiler.semantics;

import java.util.ArrayList;
import java.util.Stack;
import msu.compiler.errorhandling.CompileException;

/**
 * This class essentially defines the meaning of the program and is called on
 * by the parser. The driver for the symbol table and scopes. Also includes
 * symantic actions.
 *
 * @author Wesley
 */
public class SemanticAnalyzer {

    // If there is a lexical error turn off code generation
    private Stack<SymbolTable> scopeStack = new Stack();
    private SymbolTable currentScope;

    //---------------------------------------------
    // Symbol Table Wrapper/management methods
    //---------------------------------------------
    public void createScope(String scopeName) {
        SymbolTable newScope = new SymbolTable(scopeName);

        if (currentScope == null) {
            currentScope = newScope;
            currentScope.nestLevel = 0;
        } else {
            newScope.setParent(currentScope);
            //declare any variables made in the function/procedure heading
            //newScope.insertSymbol(currentScope.lookupSymbol(newScope.name));
            newScope.nestLevel = currentScope.nestLevel + 1;
            currentScope = newScope;
        }
        scopeStack.add(currentScope);
    }

    public void createScope(String scopeName, String label) {
        SymbolTable newScope = new SymbolTable(scopeName, label);

        if (currentScope == null) {
            currentScope = newScope;
            currentScope.nestLevel = 0;
        } else {
            newScope.setParent(currentScope);
            //declare any variables made in the function/procedure heading
            //newScope.insertSymbol(currentScope.lookupSymbol(newScope.name));
            newScope.nestLevel = currentScope.nestLevel + 1;
            currentScope = newScope;
        }
        scopeStack.add(currentScope);
    }

    public void destroyScope() {
        if (currentScope.parent != null) {
            currentScope = currentScope.parent;
        } else {
            currentScope = null;
        }
    }

    public void insertSymbol(Symbol symbol) {
        try {
            currentScope.insertSymbol(symbol);
        } catch (CompileException e) {
            e.printMessage();
        }
    }

    public Symbol lookupSymbol(String lexeme) throws CompileException {
        SymbolTable tablePointer = currentScope;
        Symbol symbol;
        do {
            symbol = tablePointer.lookupSymbol(lexeme);
            if (symbol != null) {
                return symbol;
            }
            tablePointer = tablePointer.getParent();
        } while (tablePointer != null);
        // if we get this far without returning then the identifier has not been
        // declared yet so handle an undeclared error!
        throw new CompileException((lexeme + " has not been declared!"));
    }

    public int lookupSymbolDepth(String lexeme) {
        SymbolTable tablePointer = currentScope;
        Symbol symbol;
        do {
            symbol = tablePointer.lookupSymbol(lexeme);
            if (symbol != null) {
                return tablePointer.nestLevel;
            }
            tablePointer = tablePointer.getParent();
        } while (tablePointer != null);
        return -1;
    }

    public int getTableSize() {
        return currentScope.getSize();
    }


    public SymbolTable getCurrentScope() {
        return currentScope;
    }

    public void printSymbolTable() {
        Stack<SymbolTable> tempStack = (Stack) scopeStack.clone();

        System.out.println("--------------------------");
        System.out.println("Printing the symbol tables");
        System.out.println("--------------------------");

        printStack(tempStack);

        System.out.println("--------------------------");
    }

    private void printStack(Stack<SymbolTable> stack) {
        if (stack != null && stack.size() > 0) {
            SymbolTable topTable = stack.pop();
            System.out.println("\n");
            System.out.println(topTable.toString());
            System.out.println("-------------------------------------------------------------------------------------------------");
            System.out.printf("%25s|%15s|%10s|%5s|%6s|%15s\n", "Name", "Kind", "Type", "Size", "Offset", "Label");
            System.out.println("-------------------------------------------------------------------------------------------------");
            Object[] keys = topTable.symbols.keySet().toArray();
            for (int i = 0; i < topTable.symbols.keySet().size(); i++) {
                Symbol s = topTable.symbols.get(keys[i].toString());
                System.out.printf("%25s|%15s|%10s|%5s|%6s|%15s\n", s.getLexeme(), s.kind, s.getTypeString(), s.size, s.offset, s.label);
            }
            printStack(stack);
        }
    }

    public void addReturnSpacer() {
        currentScope.incrementSize();
    }

    public ArrayList<Symbol> getTableSymbols(int tablelevel){
        ArrayList<Symbol> symbolList = new ArrayList();
        boolean found = false;
        SymbolTable tablePointer = scopeStack.peek();
        while(tablePointer != null || found == true){
            if(tablePointer.nestLevel == tablelevel)
            {
                Object[] keys = tablePointer.symbols.keySet().toArray();
                for (int i = 0; i < tablePointer.symbols.keySet().size(); i++) {
                    Symbol s = tablePointer.symbols.get(keys[i].toString());
                    symbolList.add(s);
                }
                return symbolList;
            }
            else
            {
                tablePointer = tablePointer.getParent();
            }
        }
        return null;
    }

    public int getSizeParameters(int tablelevel) {
        boolean found = false;
        int paramCount = 0;

        SymbolTable tablePointer = scopeStack.peek();
        while(tablePointer != null && !found){
            paramCount = 0;
            if(tablePointer.nestLevel == tablelevel)
            {
                found = true;
                Object[] keys = tablePointer.symbols.keySet().toArray();
                for (int i = 0; i < tablePointer.symbols.keySet().size(); i++) {
                    Symbol s = tablePointer.symbols.get(keys[i].toString());
                    if (s.kind.equalsIgnoreCase("Parameter"))
                    {
                        paramCount++;
                    }
                }
            }
            else
            {
                tablePointer = tablePointer.getParent();
            }
        }

        return paramCount;
    }

    public int getParamType()
    {
        return -1;
    }

    public int getSizeLocals(int tablelevel) {
        boolean found = false;
        int localCount = 0;

        SymbolTable tablePointer = scopeStack.peek();
        while(tablePointer != null && !found){
            localCount = 0;
            if(tablePointer.nestLevel == tablelevel)
            {
                found = true;
                Object[] keys = tablePointer.symbols.keySet().toArray();
                for (int i = 0; i < tablePointer.symbols.keySet().size(); i++) {
                    Symbol s = tablePointer.symbols.get(keys[i].toString());
                    if (s.kind.equalsIgnoreCase("Variable"))
                    {
                        localCount++;
                    }
                }
            }
            else
            {
                tablePointer = tablePointer.getParent();
            }
        }

        return localCount;
    }
}
